import tkinter as tk
from tkinter import ttk,messagebox
from datetime import datetime, timedelta
import json
import os
import time
import MetaTrader5 as mt5
import sys
from concurrent.futures import ThreadPoolExecutor
from typing import List, Union
from decimal import Decimal, ROUND_DOWN
CONFIG_FILE = "trading_settings.json"  # 全局配置文件
STANDARD_TIMES = ["04:00","08:00", "12:00", "16:00", "20:00", "24:00"]  # 全局标准时间
#hours = [int(t.split(":")[0]) for t in times]
STANDARD_HOURS = [4,8,12,16,20,24]  # 全局标准时间
# 参数设置
slippage = 0.2
interval=2

class XFactorLab:
    def __init__(self, xgui,lost,symbol):
        self.xgui = xgui
        self.lost=self.fmt_num(lost)
        self.symbol=symbol
        xgui.log(symbol)



    def get_positions(self,position_type):

        all_positions = mt5.positions_get(symbol=self.symbol)
        if all_positions is None:
            return [], 0.0

        positions = []
        for pos in all_positions:
            p_type = "long" if pos.type == mt5.POSITION_TYPE_BUY else "short"
            positions.append({
                "ticket": pos.ticket,
                "type": p_type,
                "volume": pos.volume,
                "price": pos.price_open
            })

        if position_type in ("long", "short"):
            filtered_positions = [p for p in positions if p["type"] == position_type]
        elif position_type == "all":
            filtered_positions = positions
        else:
            raise ValueError("position_type 必须是 'long'、'short' 或 'all'")

        total_volume = sum(p["volume"] for p in filtered_positions)
        return filtered_positions, total_volume

    def close_by_volume(self,ticket,volume,otype):
        req = dict(
            action=mt5.TRADE_ACTION_DEAL,
            symbol=self.symbol,
            volume=volume,
            position=ticket)

        spread=mt5.symbol_info_tick(self.symbol)
        if otype=="long":
            req["type"]=mt5.ORDER_TYPE_SELL
            req["price"]=spread.bid
        else:
            req["type"]=mt5.ORDER_TYPE_BUY
            req["price"]=spread.ask

        req["type_filling"]=mt5.ORDER_FILLING_IOC

        result =mt5.order_send(req)

        if result is not None and result.retcode != mt5.TRADE_RETCODE_DONE:
            msg="平仓失败：{}=>{}，手数：{}".format(result.retcode,result.comment,volume)
            self.xgui.log(msg)
        else:
     
            msg="平仓单号：{}，手数：{}".format(ticket,volume)
            self.xgui.log(msg)
            



    def place_limit_order(self, order_info):

        if order_info["type"] == "short":
            order_type = mt5.ORDER_TYPE_SELL_LIMIT  # 空单减仓 → 买入平仓
        else:
            order_type = mt5.ORDER_TYPE_BUY_LIMIT # 多单

        request = {
            "action": mt5.TRADE_ACTION_PENDING,
            "symbol": self.symbol,
            "volume": order_info["volume"],
            "type": order_type,
            "price": order_info["price"],

            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_RETURN,
        }
    
        result = mt5.order_send(request)


        if result.retcode != mt5.TRADE_RETCODE_DONE:
            msg="挂单失败：{}=>{}".format(result.retcode,result.comment)
            #self.xgui.log(msg)
        else:
            msg="挂单成功,价格{}，手数：{}".format(order_info["price"],order_info["volume"])
            self.xgui.log(msg)
       


    def get_last_close_and_bid_price(self):
        rates = mt5.copy_rates_from_pos(self.symbol, mt5.TIMEFRAME_H1, 0, 1)
       # print(rates)
        close_price = float(rates[0]['close'])
        tick = mt5.symbol_info_tick(self.symbol)
        latest_price = float(tick.bid) 

        return close_price, latest_price

    def load_config(self):

        app_path=""
        if getattr(sys, 'frozen', False):
            app_path =os.path.abspath(os.path.dirname(sys.executable))
    
        else:
            app_path = os.path.dirname(os.path.abspath(__file__))
        
        config_path = os.path.join(app_path, 'config.json')

        with open(config_path, "r") as config_file:
            config = json.load(config_file)
            return config

    def fmt_num(self,num,digits=2):
        str_num = str(num)
        if '.' in str_num:
            return float(str_num[:str_num.index('.') + 1 + digits])
        return 


    def limit_remove(self):
        orders = mt5.orders_get()  
        if orders is None:
            msg=(f"无法获取挂单，错误代码: {mt5.last_error()}")
            self.xgui.log(msg)
        else:
            if len(orders)<=0:
                return

            msg=(f"当前有 {len(orders)} 个挂单")
            self.xgui.log(msg)

            # 遍历所有挂单并删除
            for order in orders:
                # 构建删除订单请求
                delete_request = {
                "action": mt5.TRADE_ACTION_REMOVE, # 删除订单操作
                "order": order.ticket, # 挂单的订单编号
                }

                # 发送删除订单请求
                result = mt5.order_send(delete_request)

                # 检查删除结果
                if result.retcode != mt5.TRADE_RETCODE_DONE:
                    msg=(f"删除挂单 {order.ticket} 失败，错误代码: {result.retcode}=>{result.comment}")
                    self.xgui.log(msg)   
                else:
                    msg=(f"挂单 {order.ticket} 删除成功") 
                    self.xgui.log(msg)   

    def force_close_all(self):
        positions=mt5.positions_get(symbol=self.symbol)
       
        if len(positions)>0:
            for position in positions:

                req = dict(
                action=mt5.TRADE_ACTION_DEAL,
                symbol=position.symbol,
                volume=position.volume,
                position=position.ticket
                )
                
                spread=mt5.symbol_info_tick(self.symbol)

                            
                if position.type==0:
                    req["type"]=mt5.ORDER_TYPE_SELL
                    req["price"]=spread.bid
                else:
                    req["type"]=mt5.ORDER_TYPE_BUY
                    req["price"]=spread.ask

                req["type_filling"]=mt5.ORDER_FILLING_IOC
              
                result =mt5.order_send(req)
        

                if result is not None and result.retcode != mt5.TRADE_RETCODE_DONE:
                    msg="平仓失败，错误提示：{}=>{}".format(result.retcode,result.comment)
                    self.xgui.log(msg)
                else:
                    msg="平仓单号：{}，持仓：{}".format(position.ticket,position.volume)
                    self.xgui.log(msg)
  
                   

class TradingSystemGUI:
    def __init__(self, root):

        self.mt5_logged_in = False
       

        self.root = root
        self.root.title("黄金自动化交易系统")
        self.root.geometry("900x750")

        main_frame = ttk.Frame(root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # =============================
        # 上半部分：Tab 控件和日志输出框
        # =============================
        upper_frame = ttk.Frame(main_frame)
        upper_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        self.notebook = ttk.Notebook(upper_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)

        # 四个Tab
        self.tab_defense = ttk.Frame(self.notebook)
        self.tab_entry = ttk.Frame(self.notebook)
        self.tab_breakout = ttk.Frame(self.notebook)
        self.tab_overlay = ttk.Frame(self.notebook)
        self.tab_login = ttk.Frame(self.notebook)

        self.notebook.add(self.tab_defense, text="防守管理")
        # self.notebook.add(self.tab_entry, text="常态进场")
        # self.notebook.add(self.tab_breakout, text="突破加仓")
        # self.notebook.add(self.tab_overlay, text="攻守进场")
        self.notebook.add(self.tab_login, text="登录设置")

        # 保存输入框引用
        self.login_entries = {}
        self.defense_entries = {"short": [], "long": []}
        self.entry_entries = {"short": {}, "long": {}}
        self.breakout_entries = {"short": {}, "long": {}}

        # =============================
        # 防守管理 Tab
        # =============================
        self.build_defense_tab()

        # =============================
        # 常态进场 Tab
        # =============================
        self.build_entry_tab()

        # =============================
        # 突破加仓 Tab（独立结构）
        # =============================
        self.build_breakout_tab()

         

        # =============================
        # 叠加进出场 Tab 占位
        # =============================
        label = ttk.Label(self.tab_overlay, text="攻守进场 功能模块 - 具体内容待开发")
        label.pack(expand=True)

        self.build_login_tab()



        # =============================
        # 日志输出框
        # =============================
        log_section_frame = ttk.Frame(upper_frame)
        log_section_frame.pack(fill=tk.X, pady=(10, 0))

        log_label_frame = ttk.LabelFrame(log_section_frame, text="日志输出", padding=10)
        log_label_frame.pack(fill=tk.X)

        log_container = ttk.Frame(log_label_frame)
        log_container.pack(fill=tk.X)

        self.log_text = tk.Text(log_container, height=6, wrap=tk.WORD)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        log_scrollbar = ttk.Scrollbar(log_container, orient=tk.VERTICAL, command=self.log_text.yview)
        log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.configure(yscrollcommand=log_scrollbar.set)

        # =============================
        # 页脚部分
        # =============================
        footer_frame = ttk.Frame(main_frame)
        footer_frame.pack(fill=tk.X, side=tk.BOTTOM)

        self.status_label = ttk.Label(footer_frame, text="当前状态：暂停中", font=('TkDefaultFont', 10))
        self.status_label.pack(side=tk.LEFT, padx=(10, 20))

        button_frame = ttk.Frame(footer_frame)
        button_frame.pack(side=tk.RIGHT)

        ttk.Button(button_frame, text="保存所有设置", command=self.save_settings).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="一键平仓", command=self.close_all_positions).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="启动执行", command=self.start_execution).pack(side=tk.LEFT, padx=5)
        ttk.Button(footer_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=10)

        # =============================
        # 启动加载配置
        # =============================
        self.load_settings()
        self.log("系统界面已启动，请配置相关参数。")

    def build_login_tab(self):
        frame = ttk.LabelFrame(self.tab_login, text="MT5 登录设置", padding=20)
        frame.pack(fill=tk.X, padx=10, pady=10)

        fields = [
            ("服务器 (Server)", "server"),
            ("账户 (Account)", "account"),
            ("密码 (Password)", "password"),
            ("最大亏损 (Lost)", "lost"),
            ("交易品种 (Symbol)", "symbol"),
        ]

        for label_text, key in fields:
            row = ttk.Frame(frame)
            row.pack(fill=tk.X, pady=5)
            ttk.Label(row, text=label_text, width=18).pack(side=tk.LEFT)
            entry = ttk.Entry(row, width=30, show="*" if key == "password" else None)
            entry.pack(side=tk.LEFT, padx=5)
            self.login_entries[key] = entry

        # # 默认值（可修改）
        # self.login_entries["server"].insert(0, "TradeMaxGlobal-Demo")
        # self.login_entries["account"].insert(0, "60040773")
        # self.login_entries["password"].insert(0, "Yy112233@")
        # self.login_entries["lost"].insert(0, "0.1")
        # self.login_entries["symbol"].insert(0, "XAUUSD")


        login_btn = ttk.Button(frame, text="一键登录MT5", command=self.login_mt5)
        login_btn.pack(side=tk.LEFT, padx=5, pady=5)

    # =============================
    # 防守管理
    # =============================
    def build_defense_tab(self):
        title_label = ttk.Label(self.tab_defense, text="防守设置", font=('TkDefaultFont', 12, 'bold'))
        title_label.pack(pady=10)

        short_frame = ttk.LabelFrame(self.tab_defense, text="已成交空单", padding=10)
        short_frame.pack(fill=tk.X, padx=10, pady=5)
        self.build_order_section(short_frame, ["空单减仓", "空单补仓", "空单平仓"], self.defense_entries["short"])

        long_frame = ttk.LabelFrame(self.tab_defense, text="已成交多单", padding=10)
        long_frame.pack(fill=tk.X, padx=10, pady=5)
        self.build_order_section(long_frame, ["多单减仓", "多单补仓", "多单平仓"], self.defense_entries["long"])
    
    def build_order_section(self, parent, labels, store_list):
        for label_text in labels:
            row_frame = ttk.Frame(parent)
            row_frame.pack(fill=tk.X, pady=3)

            ttk.Label(row_frame, text=label_text, width=10).pack(side=tk.LEFT, padx=5)
            # ttk.Label(row_frame, text="标准时间").pack(side=tk.LEFT)
            # time_combo = ttk.Combobox(row_frame, values=STANDARD_TIMES, width=7, state="readonly")
            # time_combo.set(STANDARD_TIMES[0])
            # time_combo.pack(side=tk.LEFT, padx=5)

            ttk.Label(row_frame, text="收盘价").pack(side=tk.LEFT)
            entry_close = ttk.Entry(row_frame, width=10)
            entry_close.pack(side=tk.LEFT, padx=5)

            ttk.Label(row_frame, text="区间下限").pack(side=tk.LEFT)
            entry_low = ttk.Entry(row_frame, width=10)
            entry_low.pack(side=tk.LEFT, padx=5)

            ttk.Label(row_frame, text="区间上限").pack(side=tk.LEFT)
            entry_high = ttk.Entry(row_frame, width=10)
            entry_high.pack(side=tk.LEFT, padx=5)

            ttk.Label(row_frame, text="分批次数").pack(side=tk.LEFT)
            entry_batch = ttk.Entry(row_frame, width=10)
            entry_batch.pack(side=tk.LEFT, padx=5)

            ttk.Label(row_frame, text="比例").pack(side=tk.LEFT)
            entry_ratio = ttk.Entry(row_frame, width=10)
            entry_ratio.pack(side=tk.LEFT, padx=5)

            store_list.append({
                #"time_combo": time_combo,
                "close": entry_close,
                "low": entry_low,
                "high": entry_high,
                "batch": entry_batch,
                "ratio": entry_ratio
            })

    # =============================
    # 常态进场
    # =============================
    def build_entry_tab(self):
        title_label = ttk.Label(self.tab_entry, text="常态进场设置", font=('TkDefaultFont', 12, 'bold'))
        title_label.pack(pady=10)

        long_frame = ttk.LabelFrame(self.tab_entry, text="做多设置", padding=10)
        long_frame.pack(fill=tk.X, padx=10, pady=5)
        self.build_entry_section(long_frame, "long")

        short_frame = ttk.LabelFrame(self.tab_entry, text="做空设置", padding=10)
        short_frame.pack(fill=tk.X, padx=10, pady=5)
        self.build_entry_section(short_frame, "short")

    def build_entry_section(self, parent, key):
        row_frame = ttk.Frame(parent)
        row_frame.pack(fill=tk.X, pady=3)

        ttk.Label(row_frame, text="标准时间").pack(side=tk.LEFT, padx=5)
        time_combo = ttk.Combobox(row_frame, values=STANDARD_TIMES, width=7, state="readonly")
        time_combo.set(STANDARD_TIMES[0])
        time_combo.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="收盘价").pack(side=tk.LEFT)
        entry_close = ttk.Entry(row_frame, width=10)
        entry_close.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="区间下限").pack(side=tk.LEFT)
        entry_low = ttk.Entry(row_frame, width=10)
        entry_low.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="区间上限").pack(side=tk.LEFT)
        entry_high = ttk.Entry(row_frame, width=10)
        entry_high.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="分批次数").pack(side=tk.LEFT)
        entry_batch = ttk.Entry(row_frame, width=10)
        entry_batch.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="仓位总数").pack(side=tk.LEFT)
        entry_volume = ttk.Entry(row_frame, width=10)
        entry_volume.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="止盈价格").pack(side=tk.LEFT)
        entry_profit = ttk.Entry(row_frame, width=10)
        entry_profit.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="止盈比例").pack(side=tk.LEFT)
        entry_ratio = ttk.Entry(row_frame, width=10)
        entry_ratio.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="硬性止损").pack(side=tk.LEFT)
        entry_lost = ttk.Entry(row_frame, width=10)
        entry_lost.pack(side=tk.LEFT, padx=5)

        self.entry_entries[key]["row1"] = {
            "time_combo": time_combo,
            "close": entry_close,
            "low": entry_low,
            "high": entry_high,
            "batch": entry_batch,
            "volume": entry_volume,
            "profit": entry_profit,
            "lost": entry_lost,
            "ratio": entry_ratio
        }

    # =============================
    # 突破加仓（新增分批次数）
    # =============================
    def build_breakout_tab(self):
        title_label = ttk.Label(self.tab_breakout, text="突破加仓设置", font=('TkDefaultFont', 12, 'bold'))
        title_label.pack(pady=10)

        long_frame = ttk.LabelFrame(self.tab_breakout, text="做多加仓设置", padding=10)
        long_frame.pack(fill=tk.X, padx=10, pady=5)
        self.build_breakout_section(long_frame, "long")

        short_frame = ttk.LabelFrame(self.tab_breakout, text="做空加仓设置", padding=10)
        short_frame.pack(fill=tk.X, padx=10, pady=5)
        self.build_breakout_section(short_frame, "short")

    def build_breakout_section(self, parent, key):
        row_frame = ttk.Frame(parent)
        row_frame.pack(fill=tk.X, pady=3)

        ttk.Label(row_frame, text="标准时间").pack(side=tk.LEFT, padx=5)
        time_combo = ttk.Combobox(row_frame, values=STANDARD_TIMES, width=7, state="readonly")
        time_combo.set(STANDARD_TIMES[0])
        time_combo.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="收盘价").pack(side=tk.LEFT)
        entry_close = ttk.Entry(row_frame, width=10)
        entry_close.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="区间下限").pack(side=tk.LEFT)
        entry_low = ttk.Entry(row_frame, width=10)
        entry_low.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="区间上限").pack(side=tk.LEFT)
        entry_high = ttk.Entry(row_frame, width=10)
        entry_high.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="分批次数").pack(side=tk.LEFT)
        entry_batch = ttk.Entry(row_frame, width=10)
        entry_batch.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="加仓总数").pack(side=tk.LEFT)
        entry_volume = ttk.Entry(row_frame, width=10)
        entry_volume.pack(side=tk.LEFT, padx=5)

        ttk.Label(row_frame, text="硬性止损").pack(side=tk.LEFT)
        entry_lost = ttk.Entry(row_frame, width=10)
        entry_lost.pack(side=tk.LEFT, padx=5)
        

        self.breakout_entries[key]["row1"] = {
            "time_combo": time_combo,
            "close": entry_close,
            "low": entry_low,
            "high": entry_high,
            "batch": entry_batch,
            "lost": entry_lost,
            "volume": entry_volume
        }

    # =============================
    # 配置文件保存与加载
    # =============================
    def split_price_range(self,start_price, end_price, batches):
        """
        根据区间和批次数，计算每批的平均价格列表。
        例如：start=3990, end=3998, batches=5
        返回 [3990.0, 3992.0, 3994.0, 3996.0, 3998.0]
        """

        start_price = float(start_price)
        end_price = float(end_price)
        batches = int(batches)


        if batches <= 1:
            return [round((start_price + end_price) / 2, 2)]
        
        step = (end_price - start_price) / (batches - 1)
        prices = [round(start_price + i * step, 2) for i in range(batches)]
        return prices


        # 示例：
        #print(split_price_range(3990, 3998, 5))




    def prepare_reduction_orders(self,
        current_short_volume: Union[str, float],
        reduction_pct: Union[str, float],
        wait_low: Union[str, float],
        wait_high: Union[str, float],
        batches: Union[str, int],
        min_lot: float = 0.01,
        lot_step: float = 0.01,
        price_tick: float = 0.01,
        prefer_descending_prices: bool = True
    ) -> List[dict]:
        """
        生成分批减仓计划（平空），支持任意总仓位和任意百分比。
        
        参数:
        - current_short_volume: 当前总空仓手数（浮点或字符串）
        - reduction_pct: 减仓比例 0~1（浮点或字符串）
        - wait_low, wait_high: 目标区间价格（浮点或字符串）
        - batches: 分几批减仓
        - min_lot: 最小手数
        - lot_step: 手数步进
        - price_tick: 价格刻度（限价挂单）
        - prefer_descending_prices: True -> 价格从高到低，False -> 从低到高
        返回:
        - orders: List[dict] 每项 {'price': float, 'volume': float, 'action': str}
        """
        # 转换输入
        cur = float(current_short_volume)
        pct = float(reduction_pct)
        low = float(wait_low)
        high = float(wait_high)
        b = int(batches)
        
        if not (0 < pct <= 1):
            raise ValueError("减仓比例 reduction_pct 必须在 (0,1] 之间")
        if cur <= 0 or b <= 0:
            raise ValueError("总仓位和批次数必须大于0")
        
        # 计算总减仓量
        total_to_close = cur * pct
        
        # 转为步进整数（按交易步进）
        total_steps = round(total_to_close / lot_step)
        if total_steps == 0:
            # 小于最小步进，无法下单
            return []

        # 平分步数到每批
        base = total_steps // b
        rem = total_steps % b
        steps_per_batch = [base + 1 if i < rem else base for i in range(b)]
        
        # 生成价格序列
        if prefer_descending_prices:
            price_points = [high + (low - high) * i / max(b - 1, 1) for i in range(b)]
        else:
            price_points = [low + (high - low) * i / max(b - 1, 1) for i in range(b)]
        # 按价格刻度对齐
        price_points = [round(round(p / price_tick) * price_tick, 8) for p in price_points]
        
        # 生成订单
        orders = []
        for steps, p in zip(steps_per_batch, price_points):
            if steps <= 0:
                continue
            vol = max(min_lot, steps * lot_step)
            orders.append({
                'price': round(p, 2),
                'volume': round(vol, 8)
               # 'action': 'limit_buy_to_close'
            })
        
        # 校正最后一笔，保证总量精确
        sum_vol = sum(o['volume'] for o in orders)
        diff = round(total_to_close - sum_vol, 8)
        if orders and abs(diff) > 1e-8:
            orders[-1]['volume'] += diff

        return orders


    def read_settings(self):

        data = {"login": {}, "defense": {"short": [], "long": []},
                "entry": {"short": [], "long": []},
                "breakout": {"short": [], "long": []}}


        for key, widget in self.login_entries.items():
            data["login"][key] = widget.get()

        # 防守管理
        for key in ["short", "long"]:
            for item in self.defense_entries[key]:
                data["defense"][key].append({
                   # "standard_time": item["time_combo"].get(),
                    "close": item["close"].get(),
                    "low": item["low"].get(),
                    "high": item["high"].get(),
                    "batch": item["batch"].get(),
                    "ratio": item["ratio"].get()
                })

        # 常态进场
        for key in ["short", "long"]:
            for item in self.entry_entries[key].values():
                data["entry"][key].append({
                    "standard_time": item["time_combo"].get(),
                    "close": item["close"].get(),
                    "low": item["low"].get(),
                    "high": item["high"].get(),
                    "batch": item["batch"].get(),
                    "volume": item["volume"].get(),
                    "profit": item["profit"].get(),
                      "lost": item["lost"].get(),
                    "ratio": item["ratio"].get()
                })

        # 突破加仓
        for key in ["short", "long"]:
            for item in self.breakout_entries[key].values():
                data["breakout"][key].append({
                    "standard_time": item["time_combo"].get(),
                    "close": item["close"].get(),
                    "low": item["low"].get(),
                    "high": item["high"].get(),
                    "batch": item["batch"].get(),
                     "lost": item["lost"].get(),
                    "volume": item["volume"].get()
                })


        return data
    
     # ---------- MT5 操作 ----------
    def login_mt5(self):
        if mt5 is None:
            messagebox.showerror("错误", "未检测到 MetaTrader5 模块，请先：pip install MetaTrader5")
            return

        server = self.login_entries["server"].get().strip()
        account = self.login_entries["account"].get().strip()
        password = self.login_entries["password"].get().strip()
        lost = self.login_entries["lost"].get().strip()
        symbol = self.login_entries["symbol"].get().strip()

        if not server or not account or not password or not lost or not symbol:
            messagebox.showwarning("参数缺失", "请填写 server/account/password/lost/symbol")
            return

        self.log("尝试初始化 MT5 ...")

      
        
      
       

        if not mt5.initialize(login=int(account), server=server,password=password):
            #messagebox.showerror("登陆失败，错误提示：",mt5.last_error())
            msg=f"登陆失败，错误提示：{mt5.last_error()}"

            self.log(msg)
        else:
            self.log(f"MT5 登录成功: {account} on {server}")
            self.xlab=XFactorLab(self,lost,symbol)
            self.mt5_logged_in=True
            self.save_settings()

 


    def save_settings(self):
        self.log("执行：保存所有设置")

        data=self.read_settings()
        

        try:
            with open(CONFIG_FILE, "w", encoding="utf-8") as f:
                json.dump(data, f, ensure_ascii=False, indent=4)
            self.log("配置已保存")
        except Exception as e:
            self.log(f"保存配置失败: {e}")

    def load_settings(self):
        if not os.path.exists(CONFIG_FILE):
            return
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                data = json.load(f)

            login_data = data.get("login", {})
            for key, widget in self.login_entries.items():
                widget.delete(0, tk.END)
                widget.insert(0, login_data.get(key, ""))

            # 防守管理
            for key in ["short", "long"]:
                for item_widget, item_data in zip(self.defense_entries[key], data.get("defense", {}).get(key, [])):
                   # item_widget["time_combo"].set(item_data.get("standard_time", STANDARD_TIMES[0]))
                    for field in ["close", "low", "high", "batch", "ratio"]:
                        item_widget[field].delete(0, tk.END)
                        item_widget[field].insert(0, item_data.get(field, ""))

            # 常态进场
            for key in ["short", "long"]:
                for item_widget, item_data in zip(self.entry_entries[key].values(), data.get("entry", {}).get(key, [])):
                    item_widget["time_combo"].set(item_data.get("standard_time", STANDARD_TIMES[0]))
                    for field in ["close", "low", "high", "batch", "volume", "profit", "ratio","lost"]:
                        item_widget[field].delete(0, tk.END)
                        item_widget[field].insert(0, item_data.get(field, ""))

            # 突破加仓
            for key in ["short", "long"]:
                for item_widget, item_data in zip(self.breakout_entries[key].values(), data.get("breakout", {}).get(key, [])):
                    item_widget["time_combo"].set(item_data.get("standard_time", STANDARD_TIMES[0]))
                    for field in ["close", "low", "high", "batch", "volume","lost"]:
                        item_widget[field].delete(0, tk.END)
                        item_widget[field].insert(0, item_data.get(field, ""))

            self.log("配置已加载")

        except Exception as e:
            self.log(f"加载配置失败: {e}")

    # =============================
    # 其他通用功能
    # =============================
    def close_all_positions(self):
        self.log("执行：一键平仓")

        self.xlab.force_close_all()

    def start_execution(self):
        
        if not self.mt5_logged_in:
             self.log("请先录MT5,并开启算法交易")

             return 


        
        self.log("执行：启动执行")

        self.update_status("运行中")
        self.xlab.limit_remove()
        self.save_settings()
        self.trade_settings=self.read_settings()
        self.activate()



       


    def activate(self):

        self.executor = ThreadPoolExecutor()
        self.executor.submit(self.tab_defense_pos_short_reduce) 
        self.executor.submit(self.tab_defense_pos_short_add) 
        self.executor.submit(self.tab_defense_pos_short_close)

        self.executor.submit(self.tab_defense_pos_long_reduce)
        self.executor.submit(self.tab_defense_pos_long_add) 
        self.executor.submit(self.tab_defense_pos_long_close) 

        # self.executor.submit(self.tab_entry_long)
        # self.executor.submit(self.tab_breakout_long)  
    
        # self.executor.submit(self.tab_breakout_short)  

        #self.tab_defense_pos_long_close()


        
    def tab_breakout_short(self):
        cfg=self.trade_settings

        idx=0
        block="breakout"
        signal="short"
        
        close=cfg[block][signal][idx]["close"]

        close_time=cfg[block][signal][idx]["standard_time"]
        low=cfg[block][signal][idx]["low"]
        high=cfg[block][signal][idx]["high"]
        batch=cfg[block][signal][idx]["batch"]   
        volume=cfg[block][signal][idx]["volume"]
        lost=cfg[block][signal][idx]["lost"]
        

        lost = int(lost)
        close = int(close)
        batch = int(batch)
        low = float(low)
        high = float(high)
        volume = float(volume)
        
 
        prepare=self.prepare_reduction_orders(volume, 1,low, high,batch)#prefer_descending_prices=False
   
 
        self.log(f"突破做空=>{prepare}")

        
        while True:
            self.log(f"突破做空=>{prepare}")
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
            a,b,hour=self.get_time_range(close_time)

            if latest>lost:
                self.xlab.force_close_all()
      
            if hour>=a and hour<b and close_h1<close and latest>low and latest<high:
                i = 0
                while i < len(prepare):
               
                    order = prepare[i]
                    order["type"] ="short"
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    if min_p <= close_price <= max_p:
                         del prepare[i]
                         self.xlab.place_limit_order(order)
                
            time.sleep(60)
 

    def tab_breakout_long(self):
        cfg=self.trade_settings

        idx=0
        block="breakout"
        signal="long"
        
        close=cfg[block][signal][idx]["close"]

        close_time=cfg[block][signal][idx]["standard_time"]
        low=cfg[block][signal][idx]["low"]
        high=cfg[block][signal][idx]["high"]
        batch=cfg[block][signal][idx]["batch"]   
        volume=cfg[block][signal][idx]["volume"]
        lost=cfg[block][signal][idx]["lost"]
        

        lost = int(lost)
        close = int(close)
        batch = int(batch)
        low = float(low)
        high = float(high)
        volume = float(volume)
        
 
        prepare=self.prepare_reduction_orders(volume, 1,low, high,batch)#prefer_descending_prices=False
   
 
        self.log(f"突破做多=>{prepare}")

        
        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
            a,b,hour=self.get_time_range(close_time)

            if lost<latest:
                self.xlab.force_close_all()
      
            if hour>=a and hour<b and close_h1>close and latest>low and latest<high:
                i = 0
                while i < len(prepare):
               
                    order = prepare[i]
                    order["type"] ="long"
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    if min_p <= close_price <= max_p:
                         del prepare[i]
                         self.xlab.place_limit_order(order)
                
            time.sleep(60)
 




    def tab_entry_short(self):
        cfg=self.trade_settings

        idx=0
        block="entry"
        signal="short"
        
        close=cfg[block][signal][idx]["close"]

        close_time=cfg[block][signal][idx]["standard_time"]
        low=cfg[block][signal][idx]["low"]
        high=cfg[block][signal][idx]["high"]
        batch=cfg[block][signal][idx]["batch"]   
        ratio=cfg[block][signal][idx]["ratio"]
        volume=cfg[block][signal][idx]["volume"]
        profit=cfg[block][signal][idx]["profit"]
        lost=cfg[block][signal][idx]["lost"]
        

        lost = int(lost)
        profit = int(profit)
        close = int(close)
        ratio = float(ratio)
        batch = int(batch)
        low = float(low)
        high = float(high)
        volume = float(volume)
        
 
        prepare=self.prepare_reduction_orders(volume, 1,low, high,batch)#prefer_descending_prices=False
   
 
        self.log(f"常态做空=>{prepare}")

        
        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
            a,b,hour=self.get_time_range(close_time)

            if latest>lost or profit>latest:
               self.xlab.force_close_all()
      
            if hour>=a and hour<b and close_h1>close and latest>low and latest<high:
                i = 0
                while i < len(prepare):
               
                    order = prepare[i]
                    order["type"] ="short"
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    if min_p <= close_price <= max_p:
                         del prepare[i]
                         self.xlab.place_limit_order(order)
                
            time.sleep(60)


    def tab_entry_long(self):
        cfg=self.trade_settings

        idx=0
        block="entry"
        signal="long"
        
        close=cfg[block][signal][idx]["close"]

        close_time=cfg[block][signal][idx]["standard_time"]
        low=cfg[block][signal][idx]["low"]
        high=cfg[block][signal][idx]["high"]
        batch=cfg[block][signal][idx]["batch"]   
        ratio=cfg[block][signal][idx]["ratio"]
        volume=cfg[block][signal][idx]["volume"]
        profit=cfg[block][signal][idx]["profit"]
        lost=cfg[block][signal][idx]["lost"]
        

        lost = int(lost)
        profit = int(profit)
        close = int(close)
        ratio = float(ratio)
        batch = int(batch)
        low = float(low)
        high = float(high)
        volume = float(volume)
        
 
        prepare=self.prepare_reduction_orders(volume, 1,low, high,batch)#prefer_descending_prices=False
   
 
        self.log(f"常态做多=>{prepare}")

        
        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
            a,b,hour=self.get_time_range(close_time)

            if lost<latest or profit>latest:
                self.xlab.force_close_all()
      
            if hour>=a and hour<b and close_h1>close and latest>low and latest<high:
                i = 0
                while i < len(prepare):
               
                    order = prepare[i]
                    order["type"] ="long"
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    if min_p <= close_price <= max_p:
                         del prepare[i]
                         self.xlab.place_limit_order(order)
                
            time.sleep(60)
 







    def allocate_reduction_to_positions(self,positions, reduction_orders, reduction_pct, position_type="short"):
        pos_list = [p for p in positions if p["type"] == position_type]
        if not pos_list:
            return []

        # 每个持仓的目标减仓量
        for p in pos_list:
            p["reduce_target"] = round(p["volume"] * reduction_pct, 8)

        expanded_orders = []
        pos_idx = 0
        for order in reduction_orders:
            remaining = order["volume"]
            while remaining > 1e-8 and pos_idx < len(pos_list):
                p = pos_list[pos_idx]
                if p["reduce_target"] <= 1e-8:
                    pos_idx += 1
                    continue

                use_vol = min(remaining, p["reduce_target"])
                p["reduce_target"] -= use_vol
                remaining -= use_vol

                expanded_orders.append({
                    "ticket": p["ticket"],
                    "price": order["price"],
                    "volume": round(use_vol, 8),
                    "type": position_type
                })

        return expanded_orders
    
    
    def tab_defense_pos_long_close(self):
        idx=2
        cfg=self.trade_settings
        close=cfg["defense"]["long"][idx]["close"]
       
        low=cfg["defense"]["long"][idx]["low"]
        high=cfg["defense"]["long"][idx]["high"]
        batch=cfg["defense"]["long"][idx]["batch"]   
        reduction_pct=cfg["defense"]["long"][idx]["ratio"]

        close = int(close)
        reduction_pct = float(reduction_pct)
        batch = int(batch)
        low = float(low)
        high = float(high)
        
        all_positions, total_all =self.xlab.get_positions("long")
        prepare=self.prepare_reduction_orders(total_all, reduction_pct,low, high,batch)#prefer_descending_prices=False
        mapped_orders =self.allocate_reduction_to_positions(all_positions, prepare, reduction_pct, "long")
        
 
        self.log(f"防守多单平仓=>{mapped_orders}")

     

        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
     
            if self.is_in_standard_hours() and close_h1<close and latest>low and latest<high:

                for i in range(len(mapped_orders) - 1, -1, -1):
                    order = mapped_orders[i]
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    if min_p <= latest <= max_p:
                         del mapped_orders[i]
                         self.xlab.close_by_volume(order["ticket"],order["volume"] ,order["type"])
                
            time.sleep(interval)
    

    def tab_defense_pos_long_add(self):
        idx=1
        cfg=self.trade_settings
        close=cfg["defense"]["long"][idx]["close"]
        low=cfg["defense"]["long"][idx]["low"]
        high=cfg["defense"]["long"][idx]["high"]
        batch=cfg["defense"]["long"][idx]["batch"]   
        reduction_pct=cfg["defense"]["long"][idx]["ratio"]

        close = int(close)
        reduction_pct = float(reduction_pct)
        batch = int(batch)
        low = float(low)
        high = float(high)
        
        all_positions, total_all =self.xlab.get_positions("long")
        prepare=self.prepare_reduction_orders(total_all, reduction_pct,low, high,batch)#prefer_descending_prices=False
        mapped_orders =self.allocate_reduction_to_positions(all_positions, prepare, reduction_pct, "long")
        
 
        self.log(f"防守多单补仓=>{mapped_orders}")



        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
           
            if self.is_in_standard_hours() and close_h1>close and latest>low and latest<high:
                for i in range(len(mapped_orders) - 1, -1, -1):
               
                    order = mapped_orders[i]
                    order["type"] ="long"
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    #self.log(f"{min_p}==={latest}==={max_p}")
                    if min_p <= latest <= max_p:
                         del mapped_orders[i]
                         self.xlab.place_limit_order(order)
        
            time.sleep(interval)
            

    def tab_defense_pos_long_reduce(self):
        idx=0
        cfg=self.trade_settings
        close=cfg["defense"]["long"][idx]["close"]

        low=cfg["defense"]["long"][idx]["low"]
        high=cfg["defense"]["long"][idx]["high"]
        batch=cfg["defense"]["long"][idx]["batch"]   
        reduction_pct=cfg["defense"]["long"][idx]["ratio"]

        close = int(close)
        reduction_pct = float(reduction_pct)
        batch = int(batch)
        low = float(low)
        high = float(high)
        
        all_positions, total_all =self.xlab.get_positions("long")
        prepare=self.prepare_reduction_orders(total_all, reduction_pct,low, high,batch)#prefer_descending_prices=False
        mapped_orders =self.allocate_reduction_to_positions(all_positions, prepare, reduction_pct, "long")
        
 
        self.log(f"防守多单减仓=>{mapped_orders}")



        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
            #self.log(f"{close_h1}==={latest}==={close}")
            if self.is_in_standard_hours() and close_h1<close and latest>low and latest<high:
                for i in range(len(mapped_orders) - 1, -1, -1):
                    order = mapped_orders[i]
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    #self.log(f"{min_p}==={latest}==={max_p}")
                    if min_p <= latest <= max_p:
                        del mapped_orders[i]
                        self.xlab.close_by_volume(order["ticket"],order["volume"] ,order["type"])

            time.sleep(interval)


    def tab_defense_pos_short_close(self):
        idx=2
        cfg=self.trade_settings
        close=cfg["defense"]["short"][idx]["close"]

        low=cfg["defense"]["short"][idx]["low"]
        high=cfg["defense"]["short"][idx]["high"]
        batch=cfg["defense"]["short"][idx]["batch"]   
        reduction_pct=cfg["defense"]["short"][idx]["ratio"]

        close = int(close)
        reduction_pct = float(reduction_pct)
        batch = int(batch)
        low = float(low)
        high = float(high)
        
        all_positions, total_all =self.xlab.get_positions("short")
        prepare=self.prepare_reduction_orders(total_all, reduction_pct,low, high,batch)#prefer_descending_prices=False
        mapped_orders =self.allocate_reduction_to_positions(all_positions, prepare, reduction_pct, "short")
        
 
        self.log(f"防守空单平仓=>{mapped_orders}")

        while True:
            close_h1, latest = self.xlab.get_last_close_and_bid_price()
            if self.is_in_standard_hours() and close_h1 > close and latest > low and latest < high:
                #self.log(f"防守空单平仓=>{1}")
                for i in range(len(mapped_orders) - 1, -1, -1):
           

                    order = mapped_orders[i]
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                   # self.log(f"{min_p} === {latest} === {max_p}")
                    if min_p <= latest <= max_p:
                        del mapped_orders[i]
                        self.xlab.close_by_volume(order["ticket"], order["volume"], order["type"])



            time.sleep(interval)



    def tab_defense_pos_short_add(self):
        idx=1
        cfg=self.trade_settings
        close=cfg["defense"]["short"][idx]["close"]
        
        low=cfg["defense"]["short"][idx]["low"]
        high=cfg["defense"]["short"][idx]["high"]
        batch=cfg["defense"]["short"][idx]["batch"]   
        reduction_pct=cfg["defense"]["short"][idx]["ratio"]

        close = int(close)
        reduction_pct = float(reduction_pct)
        batch = int(batch)
        low = float(low)
        high = float(high)
        
        all_positions, total_all =self.xlab.get_positions("short")
        prepare=self.prepare_reduction_orders(total_all, reduction_pct,low, high,batch)#prefer_descending_prices=False
    
        self.log(f"防守空单补仓=>{prepare}")
   


        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
           
            if self.is_in_standard_hours() and close_h1<close and latest>low and latest<high:

                for i in range(len(prepare) - 1, -1, -1):
                    order = prepare[i]
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    order["type"] ="short"
                    self.log(f"{min_p}==={latest}==={max_p}")
                    if min_p <= latest <= max_p:
                        del prepare[i]
                        self.xlab.place_limit_order(order)

                    

            time.sleep(interval)       
  

    def tab_defense_pos_short_reduce(self):
        cfg=self.trade_settings

     
        close=cfg["defense"]["short"][0]["close"]
        low=cfg["defense"]["short"][0]["low"]
        high=cfg["defense"]["short"][0]["high"]
        batch=cfg["defense"]["short"][0]["batch"]   
        reduction_pct=cfg["defense"]["short"][0]["ratio"]      


        close = int(close)
        reduction_pct = float(reduction_pct)
        batch = int(batch)
        low = float(low)
        high = float(high)
        

        all_positions, total_all =self.xlab.get_positions("short")
        reduction_orders=self.prepare_reduction_orders(total_all, reduction_pct,low, high,batch)#prefer_descending_prices=False
        mapped_orders =self.allocate_reduction_to_positions(all_positions, reduction_orders, reduction_pct, "short")


        self.log(f"防守空单减仓=>{mapped_orders}")

       
        while True:
            close_h1, latest=self.xlab.get_last_close_and_bid_price()
            #self.log(f"{close_h1}==={close}==={latest}==={close}")
            if self.is_in_standard_hours() and close_h1>close and latest>low and latest<high:

                for i in range(len(mapped_orders) - 1, -1, -1):
                    order = mapped_orders[i]
                    min_p = order["price"] - slippage
                    max_p = order["price"]
                    #self.log(f"{min_p}==={latest}==={max_p}")
                    if min_p <= latest <= max_p:
                        del mapped_orders[i]
                        self.xlab.close_by_volume(order["ticket"],order["volume"] ,order["type"])

            time.sleep(interval)
        
        

    def is_in_standard_hours(self):
        ts = datetime.now()

        hour = ts.hour

        #return True



        return hour in STANDARD_HOURS
    

    def log(self, msg):
        t = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.log_text.insert(tk.END, f"[{t}] {msg}\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()

    def clear_log(self):
        self.log_text.delete(1.0, tk.END)

    def update_status(self, status):
        self.status_label.config(text=f"当前状态：{status}")


# =============================
# 主函数
# =============================
def main():
    root = tk.Tk()
    try:
        root.state('zoomed')
    except:
        root.attributes('-zoomed', True)
    app = TradingSystemGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()
